package org.jader.bolao.statistics;

import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;

import org.jader.bolao.Championship;
import org.jader.bolao.ClassificationAlgorithm;
import org.jader.bolao.Game;
import org.jader.bolao.GameTeamed;
import org.jader.bolao.Group;
import org.jader.bolao.Phase;
import org.jader.bolao.Team;
import org.jader.bolao.Championship.Type;
import org.jader.bolao.Group.TeamPointer;
import org.jader.bolao.classificationalgorithm.ClassifyByPoints;
import org.jader.bolao.classificationalgorithm.ClassifyByTotals;
import org.jader.bolao.classificationalgorithm.ClassifyByWins;
import org.jader.bolao.gui.Application;

public class TeamedChampionshipStatistics {

	private HashMap<Group, Standings> groupStandings = new HashMap<Group, Standings>();
	private HashMap<Game, Standings> gameStandings = new HashMap<Game, Standings>();
	
	private TeamedChampionshipStatistics() {}
	
	public static TeamedChampionshipStatistics generate(Championship c) {
		if (c.getType() != Type.TEAMS) return null;
		
		TeamedChampionshipStatistics cs = new TeamedChampionshipStatistics();
		
		cs.createClassifications(c);
		
		if (Application.DEBUG_MODE) System.out.println("ChampionshipStatistics::generate() => Groups: "+cs.groupStandings.size()+"  Games: "+cs.gameStandings.size());
		return cs;
	}
	
	private void createClassifications(Championship c) {
		Iterator<Phase> itp = c.phasesIterator();
		while (itp.hasNext()) {
			Phase phase = itp.next();
			ClassificationAlgorithm ca = phase.getClassificationAlgorithm();
			// Generate group classification tables
			Iterator <Group> itg = phase.groupsIterator();
			while (itg.hasNext()) {
				Group group = itg.next();
				
				// Generate game classification tables
				Iterator<Game> itga = group.gamesIterator();
				while (itga.hasNext()) {
					GameTeamed game = (GameTeamed) itga.next();
					
					if (ca instanceof ClassifyByPoints)
						gameStandings.put(game, new StandingsByPoints(game, (ClassifyByPoints) ca));
					else if (ca instanceof ClassifyByWins)
						gameStandings.put(game, new StandingsByWins(game));
					else
						gameStandings.put(game, new StandingsByTotal(game));
				}
				

				if (ca instanceof ClassifyByTotals)
					groupStandings.put(group, new StandingsByTotal(group));
				else if (ca instanceof ClassifyByWins)
					groupStandings.put(group, new StandingsByWins(group));
				else
					groupStandings.put(group, new StandingsByPoints(group, (ClassifyByPoints) ca));
			}
		}
	}
	
	public abstract class Standings {
		
		protected LinkedList<Team> sortedTeams = new LinkedList<Team>();
		
		private Standings() {}
		
		public int getTotalTeams() {
			return sortedTeams.size();
		}
		
		public Team getTeam(int position) {
			return sortedTeams.get(position - 1);
		}
		
		public int getPosition(Team team) {
			return sortedTeams.indexOf(team) + 1;
		}
	}
	
	public class StandingsByTotal extends Standings {

		protected HashMap<Team, Long> teamsTotal = new HashMap<Team, Long>();
		
		private StandingsByTotal(GameTeamed game) {
			super();
			Iterator<Team> itt = game.teamsIterator();
			while (itt.hasNext()) {
				Team team = itt.next();
				int total = game.getTotalSum(team);
				teamsTotal.put(team, Long.valueOf(total));
				
				// Insert ordered by total
				int position = 0;
				while (position < sortedTeams.size() && 
						game.getTotalSum(sortedTeams.get(position)) > total) {
					position++;
				}
				sortedTeams.add(position, team);
			}
		}
		
		private StandingsByTotal(Group group) {
			super();
			Iterator<TeamPointer> itt = group.teamsIterator();
			while (itt.hasNext()) {
				TeamPointer teamPointer = itt.next();
				Team team = teamPointer.team();
				
				Iterator<Game> itg = group.gamesIterator();
				while (itg.hasNext()) {
					GameTeamed game = (GameTeamed) itg.next();
					Long total = teamsTotal.get(team);
					if (total == null) total = Long.valueOf(0);
					teamsTotal.put(team, Long.valueOf(total.longValue() + game.getTotalSum(team)));
				}
				
				// Insert ordered by total
				int position = 0;
				Long total = teamsTotal.get(team);
				while (position < sortedTeams.size() && 
						teamsTotal.get(sortedTeams.get(position)) > total) {
					position++;
				}
				sortedTeams.add(position, team);
			}
		}
		
		public Long getTotalSum(Team t) {
			return teamsTotal.get(t);
		}
	}
	
	public class StandingsByWins extends StandingsByTotal {
		
		public HashMap<Team, Integer> teamWins = new HashMap<Team, Integer>();
		
		private StandingsByWins(GameTeamed game) {
			super(game);
			
			// Get winner
			Long winnerTotal = teamsTotal.get(sortedTeams.getFirst());
			Iterator<Team> itt = game.teamsIterator();
			while (itt.hasNext()) {
				Team team = itt.next();
				if (game.getTotalSum(team) == winnerTotal.intValue()) 
					teamWins.put(team, Integer.valueOf(1));
				else 
					teamWins.put(team, Integer.valueOf(0));
			}
		}
		
		private StandingsByWins(Group group) {
			super(group);
			
			// Count wins
			Iterator<Game> itg = group.gamesIterator();
			while (itg.hasNext()) {
				GameTeamed game = (GameTeamed) itg.next();
				Iterator<Team> itt = game.teamsIterator();
				while (itt.hasNext()) {
					Team team = itt.next();
					if (game.isWinner(team)) {
						Integer wins = teamWins.get(team);
						if (wins == null) wins = Integer.valueOf(0);
						teamWins.put(team, Integer.valueOf(wins.intValue() + 1));
					}
				}
			}
			
			// Reorder by wins
			Iterator<Team> itt = sortedTeams.iterator();
			while (itt.hasNext()) {
				Team team = itt.next();
				int index = sortedTeams.indexOf(team); 
				while (teamWins.get(team) < teamWins.get(sortedTeams.get(index+1))) {
					// Swap teams
					sortedTeams.remove(team);
					sortedTeams.add(++index,team);
				}
			}
		}
		
		public int getWins(Team t) {
			return teamWins.get(t).intValue();
		}
	}
	
	public class StandingsByPoints extends StandingsByTotal {
		
		private HashMap<Team, Float> teamPoints = new HashMap<Team, Float>();
		
		private StandingsByPoints(GameTeamed game, ClassifyByPoints ca) {
			super(game);
			
			// Calculate points by total
			HashMap<Long, Float> pointsByTotal = new HashMap<Long, Float>();
			Long total = null;
			int totalCounter = 0;
			int index = -1;
			while (++index < sortedTeams.size()) {
				total = teamsTotal.get(sortedTeams.get(index));
				totalCounter = 1;
				int points = ca.getPointsFor(index + 1);
				while (total == teamsTotal.get(sortedTeams.get(index + 1))) {
					index++;
					totalCounter++;
					points += ca.getPointsFor(index + 1);
				}
				pointsByTotal.put(total, Float.valueOf(points / totalCounter));
			}
			
			// Distribute points
			Iterator<Team> itt = game.teamsIterator();
			while (itt.hasNext()) {
				Team team = itt.next();
				teamPoints.put(team, pointsByTotal.get(teamsTotal.get(team)));
			}
		}
		
		private StandingsByPoints(Group group, ClassifyByPoints ca) {
			super(group);
			
			// Calculate total points
			Iterator<Game> itg = group.gamesIterator();
			while (itg.hasNext()) {
				GameTeamed game = (GameTeamed) itg.next();
				StandingsByPoints standings = new StandingsByPoints(game, ca);
				Iterator<Team> itt = game.teamsIterator();
				while (itt.hasNext()) {
					Team team = itt.next();
					Float points = teamPoints.get(team);
					if (points == null) points = Float.valueOf(0);
					teamPoints.put(team, Float.valueOf(points.floatValue() + standings.getPoints(team)));
				}
			}
			
			// Reorder by points
			Iterator<Team> itt = sortedTeams.iterator();
			while (itt.hasNext()) {
				Team team = itt.next();
				int index = sortedTeams.indexOf(team); 
				while (teamPoints.get(team) < teamPoints.get(sortedTeams.get(index+1))) {
					// Swap teams
					sortedTeams.remove(team);
					sortedTeams.add(++index,team);
				}
			}
		}
		
		public Float getPoints(Team team) {
			return teamPoints.get(team);
		}
	}
}
